<?php

/**
 * Functiones utilitarias para manejar los codigos de producto y los id de los componentes.
 *
 */
class Ext_Tracker_ProductComponentCode
{
    // La suma de PRODUCT_CODE_SIZE + COMPONENT_CODE_SIZE debe ser menor que 19 
    // que es el espacio reservado en la base de datos
    const PRODUCT_CODE_SIZE = '7';
    // Para calcular el component id hago xor de todo, asi que deben poder codificarse tantos bytes.
    // La funcion encode_id es dependiente de este numero: Hago la cuenta en bytes y despues 
    // recalculo para que siempre quede un digito base 36.
    const COMPONENT_CODE_SIZE = '1';

    
    /**
     * Toma un codigo, lo fracciona en productId y componentId
     *
     * @param string $code
     * @return array
     */
    static public function getIds( $code )
    {
        $code = strtoupper( $code );
        $matches = array();
        if ( preg_match( "/^([0-9A-Z]{1," . self::PRODUCT_CODE_SIZE . "}){1}(-([0-9A-Z]{" . self::COMPONENT_CODE_SIZE . "}){1}){0,1}$/", $code, $matches ) != 1 ) {
            throw new Exception( "El codigo debe estas compuesto por " . self::PRODUCT_CODE_SIZE . " caracteres un guion y " . self::COMPONENT_CODE_SIZE . " caracteres" );
        }
        $product = $matches[ 1 ];
        $component = "";
        if ( isset( $matches[ 3 ] ) ) {
            $component = $matches[ 3 ];
        }
        return array( $product, $component 
        );
    }

    
    static function getProductId( $code )
    {
        $matches = self::getIds( $code );
        return $matches[ 0 ];
    }

    static function getComponentId( $code )
    {
        $matches = self::getIds( $code );
        return self::decodeId( $matches[ 1 ], $matches[ 0 ] );
    }

    static function getCode( $productId, $componentId )
    {
        return $productId . "-" . self::encodeId( $componentId, $productId );
    }

    static private function rotate( $in )
    {
        $itmp = $in;
        for( $i = 0; $i < strlen( $in ); $i ++ ) {
            $itmp[ $i ] = $in[ strlen( $in ) - $i - 1 ];
        }
        return $itmp;
    }

    /**
     * 
     *
     * @param string $id base36 encoded
     * @param string $key base36 encoded int
     * @param boolean $encode encode/decode
     * @return string $ret 
     */
    static private function internalEncoder( $id36, $key, $encode )
    {
        $xorWidth = floor( self::COMPONENT_CODE_SIZE * log( 36, 2 ) );
        $id2 = str_pad( base_convert( $id36, 36, 2 ), $xorWidth, '0', STR_PAD_LEFT );
        $key2 = base_convert( $key, 36, 2 );
        $key2parts = str_split( $key2, $xorWidth );
        
        //      $part = substr( $part, 0, strlen( $part ) - 4 ) . substr( $part, strlen( $part ) - 2 ) . substr( $part, strlen( $part ) - 4, 2 );
        foreach ( $key2parts as $part ) {
            if ( $encode ) {
                $id2 = self::rotate( $id2 );
            }
            $part = str_pad( $part, $xorWidth, '0', STR_PAD_LEFT );
            for( $i = 0; $i < $xorWidth; $i ++ ) {
                $id2[ $i ] = intval( $id2[ $i ] ) ^ intval( $part[ $i ] );
            }
            if ( ! $encode ) {
                $id2 = self::rotate( $id2 );
            }
        }
        return base_convert( $id2, 2, 36 );
    }

    /**
     * Used to encode an id with a key (here is the productId)
     *
     * @param string $id 
     * @param string $key
     * @return string
     */
    static public function encodeId( $id, $key )
    {
        $id36 = base_convert( $id, 10, 36 );
        $xorWidth = floor( self::COMPONENT_CODE_SIZE * log( 36, 2 ) );
        if ( $id >= pow( 2, $xorWidth ) ) {
            throw new Exception( "el id $id es demasiado grande" );
        }
        $ret36 = self::internalEncoder( $id36, $key, true );
        return strtoupper( sprintf( "%0" . self::COMPONENT_CODE_SIZE . "s", $ret36 ) );
    }

    /**
     * Used to decode an encoded id with a key
     *
     * @param string $code
     * @param string $key
     * @return string
     */
    static public function decodeId( $id36, $key )
    {
        $ret36 = self::internalEncoder( $id36, $key, false );
        $id = base_convert( $ret36, 36, 10 );
        return $id;
    }

    static public function intToProductId( $intId )
    {
        return strtoupper( sprintf( "%0" . self::PRODUCT_CODE_SIZE . "s", base_convert( $intId, 10, 36 ) ) );
    }

    static public function productIdToInt( $productId )
    {
        return base_convert( $productId, 36, 10 );
    }
}